using System;
using System.Linq.Expressions;
using System.Reflection;
using UnityEngine;
using UnityEngine.UI;

public delegate object BuildFuncParam<T>(T param);

public class BindBuilder<ForType, VMType>
    : IBuild
    where ForType : class where VMType : BaseViewModel
{
    public ForType Target { get; private set; }
    public VMType ViewModel { get; private set; }
    public EBindType BindType { get; private set; }

    private string forName;
    private string toName;
    private BuildFuncParam<ForType> forFunc;
    private BuildFuncParam<VMType> toFunc;

    private bool ignoreVM2View;

    public BindBuilder(ForType target, VMType viewModel)
    {
        Target = target;
        ViewModel = viewModel;
        OneWay();
    }

    public BindBuilder<ForType, VMType> For(Expression<BuildFuncParam<ForType>> func)
    {
        forFunc = func.Compile();
        forName = ExpressionParser.ParseMemberName(func);
        return this;
    }

    public BindBuilder<ForType, VMType> To(Expression<BuildFuncParam<VMType>> func)
    {
        toFunc = func.Compile();
        toName = ExpressionParser.ParseMemberName(func);
        return this;
    }

    public void Build()
    {
        switch (BindType)
        {
            case EBindType.OneWay:
                BindVM2View();
                break;
            case EBindType.TwoWay:
                BindVM2View();
                BindView2VM();
                break;
        }
    }

    protected void BindVM2View()
    {
        ViewModel.OnValueChange += OnValueChange;
        OnValueChange(toFunc(ViewModel), toName);
    }

    protected void BindView2VM()
    {
        if (Target == null) return;
        InputField inputField = Target as InputField;
        if (inputField != null)
        {
            inputField.onValueChanged.AddListener((str) =>
            {
                Type type = ViewModel.GetType();
                PropertyInfo propertyInfo = type.GetProperty(toName);
                if (propertyInfo != null)
                {
                    SuppressVM2View(() =>
                    {
                        propertyInfo.SetValue(ViewModel, str);
                    });
                }
            });
        }
        else
        {
            Debug.LogError("未匹配类型");
        }
    }

    public void SuppressVM2View(Action action)
    {
        ignoreVM2View = true;
        action?.Invoke();
        ignoreVM2View = false;
    }

    protected void OnValueChange(object newVal, string name)
    {
        if (ignoreVM2View) return;
        if (!toName.Equals(name)) return;

        Type type = Target.GetType();
        if (type != null)
        {
            MemberInfo[] memberInfoList = type.GetMember(forName);
            if (memberInfoList != null)
            {
                foreach (MemberInfo memberInfo in memberInfoList)
                {
                    PropertyInfo propertyInfo = memberInfo as PropertyInfo;
                    if (propertyInfo != null)
                    {
                        propertyInfo.SetValue(Target, newVal);
                        continue;
                    }
                    FieldInfo fieldInfo = memberInfo as FieldInfo;
                    if (fieldInfo != null)
                    {
                        fieldInfo.SetValue(Target, newVal);
                        continue;
                    }
                }
                return;
            }
        }
    }

    public BindBuilder<ForType, VMType> OneWay()
    {
        BindType = EBindType.OneWay;
        return this;
    }

    public BindBuilder<ForType, VMType> TwoWay()
    {
        BindType = EBindType.TwoWay;
        return this;
    }
}

public enum EBindType
{
    OneWay = 1,
    TwoWay = 2
}
